/**
 *	@author: Manimin Morga
 *	date created: January 6, 2006
 *	version: 1.07
 *	modified: June 4, 2006
 *	modification:
 *		ignore whitespace only sentences
 *		ensures there is no null value to be added - not sure where it is added
 */
 
package text.ia;

import java.util.Vector;
import java.util.regex.Pattern;

/**
 * Method that uses regular expressions in order to
 * identify sentence boundaries.
 * 
 * @author <a href="mailto:Min_Morgz@yahoo.com">Manimin R. Morga</a> 
 */
public class RegularExpressions {
	private String strText;
	private Vector vSegmentedText;
	private TExtLexicon lex;

	/**
	 * Constructor.
	 * 
	 * @param strOrigText Text without sentence boundaries. 
	 * @param cTarget The target language of the strOrigText. Ex. 'e' for English and 'f' for Filipino
	 */
	public RegularExpressions(String strOrigText, char cTarget) {
		strText = strOrigText;
		vSegmentedText = null;
		lex = new TExtLexicon(cTarget);
	}

	/**
	 * Verify if its a number.
	 * 
	 * @return true if it is, false otherwise. 
	 */
	protected boolean isNumber(String strWord) {
		//return Pattern.matches("[(]*[\"]*[(]*[A-Z]*[$]*([0-9]+[.])+", strToken);
		return Pattern.matches("[(]*[\"]*[(]*[P]*[$]*[0-9]*[.]?[0-9]+", strWord);
	}

	/**
	 * Verify if the word is in the list of regular expressions.
	 * 
	 * @param strWord is a word to verify if it is in the regular expressions database.
	 * 
	 * @return true if it is, false otherwise. 
	 */
	protected boolean isAbbrev(String strWord) {
		boolean isInDB = lex.lookupAbbrev(strWord.replace("'", "''"));
		return isInDB;
	}

	/**
	 * Verify if the word is in the list of regular expressions.
	 * 
	 * @param StringBuffer strbSentence which contains the sentence 
	 *			to be appended to the list of segmented sentences
	 * 
	 * @return StringBuffer always a new StringBuffer object
	 */
	protected StringBuffer addNewSegmentedSentence(StringBuffer strbSentence) {
		//ensures that the current sentence to be appended is not empty
		if(strbSentence.length() > 0 && !strbSentence.toString().matches("[\\s]+")) {
			//ensures there is no space before and after the sentence
			vSegmentedText.add(strbSentence.toString().trim());
			strbSentence = new StringBuffer();
		}
		
		return strbSentence;
	}
	
	protected boolean isDecimalPtDelimiter(char cChar){
		boolean isDelimiter = false;

		if(cChar == '.' || cChar == '!' || cChar == '?' || Character.isWhitespace(cChar) || cChar == '\n')
			isDelimiter = true;

		return isDelimiter;
	}

	protected boolean isDelimiter(char cChar){
		boolean isDelimiter = false;

		if(cChar == '!' || cChar == '?' || Character.isWhitespace(cChar))
			isDelimiter = true;

		return isDelimiter;
	}

	protected StringBuffer getStartOfWord(int nIndex, int nStartLimit) {
		StringBuffer strbWord = new StringBuffer();
		
		//get the start of the word from the index including the period
		for(int i = nIndex; i >= nStartLimit && !isDelimiter(strText.charAt(i)); i--) {
			strbWord.append(strText.charAt(i));
		}

		strbWord.reverse();
		//System.out.println("start word: "+strbWord.toString());
		
		return strbWord;
	}

/**
 *	input: 12.34 56.78. .90 12.34.56 78. 90.12.34.
 *	output:
 *		12.34 56.78.
 *		.90 12.34.
 *		56 78.
 *		90.12.
 *		34.
 *	input: 12a.34 56.78.a a.90 12.34.56f 78. 90.12.34.
 *	output:
 *		12a.
 *		34 56.78.
 *		a a.
 *		90 12.34.
 *		56f 78.
 *		90.12.
 *		34.
 *	input: 1.2.3.4.5.6.7.8.9.0.
 *	output:
 *		1.2.
 *		3.4.
 *		5.6.
 *		7.8.
 *		9.0.
 */
 
	protected StringBuffer checkIfDecimalPt(int nIndex, int nPeriodIndex) {
		//System.out.println("checkIfDecimalPt");
		StringBuffer strbWithStartWord = getStartOfWord(nIndex, nPeriodIndex+1);
		StringBuffer strbWord = new StringBuffer(".");
		
		//get the end of the word from the index
		for(int i = nIndex+1; i < strText.length() && !isDecimalPtDelimiter(strText.charAt(i)); i++) {
			strbWithStartWord.append(strText.charAt(i));
			strbWord.append(strText.charAt(i));
		}

		//System.out.println("\tnIndex: "+nIndex);
		//System.out.println("\tnPeriodIndex: "+nPeriodIndex);
		//System.out.println("\tword: "+strbWord.toString());
		//System.out.println("\tcompared word: "+strbWithStartWord.toString());

		if (isNumber(strbWithStartWord.toString())) {
			//System.out.println("\t\tnumber: "+strbWithStartWord.toString());
		}else{
			strbWord = null;
		}

		return strbWord;
	}

/**
 *	input: She went at.wt.Enjoy the day. She went at.wt. tonight.He has at. wt. in the dark.She went at.wt.Enjoy.Love.Adhere to your responsibilites.
 *	output:
 *		She went at.wt.
 *		Enjoy the day.
 *		She went at.wt. tonight.
 *		He has at. wt. in the dark.
 *		She went at.wt.
 *		Enjoy.
 *		Love.
 *		Adhere to your responsibilites.
 */
	
	protected Vector checkIfPartOfAbbrev(int nIndex, int nPeriodIndex) {
		//System.out.println("checkIfPartOfAbbrev");
		Vector vWordAndPeriodIndex = null, vEllipse = null;
		StringBuffer strbWithStartWord = getStartOfWord(nIndex, nPeriodIndex+1);
		StringBuffer strbWord = new StringBuffer(".");
		StringBuffer strbEllipse;
		boolean bFound = false, bUpdatePeriod = true;
		int nLastIndex = 0;

		//get the end of the word from the index, skip the period though coz its already added prev
		for(int i = nIndex+1; i < strText.length() && !isDelimiter(strText.charAt(i)); i++) {
			strbWithStartWord.append(strText.charAt(i));
			strbWord.append(strText.charAt(i));
		}

		//System.out.println("\t1nIndex: "+nIndex);
		//System.out.println("\t1nPeriodIndex: "+nPeriodIndex);
		//System.out.println("\t1word: "+strbWord.toString());
		//System.out.println("\t1compared word: "+strbWithStartWord.toString());

		if (isAbbrev(strbWithStartWord.toString())) {
			//System.out.println("\t\t1abbrev: "+strbWithStartWord.toString());
		}else{
			//maybe there is still an abbrev but it is followed by another word,
			//which makes the period in the abbrev as also a period which indicates the EOS,
			//or it is followed by another period, which makes the period in the abbrev as not an EOS,
			while((nLastIndex = strbWithStartWord.lastIndexOf(".")) > -1 && !bFound) {
				//System.out.println("\t\t\tinside while");
				strbWithStartWord = new StringBuffer(strbWithStartWord.substring(0, nLastIndex));
				nLastIndex = strbWord.lastIndexOf(".");
				if(nLastIndex > -1) {
					strbWord = new StringBuffer(strbWord.substring(0, nLastIndex));
				}else{
					strbWord = new StringBuffer();
				}
				//System.out.println("\t\t\t2word: "+strbWord.toString());
				//System.out.println("\t\t\t2compared word: "+strbWithStartWord.toString()+".");
				
				if (isAbbrev(strbWithStartWord.toString()+".")) {
					//System.out.println("\t\t\t\t2abbrev: "+strbWithStartWord.toString()+".");
					bFound = true;
					strbWord.append(".");
					//check if the char after the abbrev is an ellipse
					if ((vEllipse = checkIfPartOfEllipse(nIndex + strbWord.length(), nPeriodIndex)) != null) {
						strbEllipse = (StringBuffer)vEllipse.get(0);
						strbWord.append(strbEllipse);
						//nPeriodIndex = nIndex + strbWord.length()-1;
						nPeriodIndex = ((Integer)vEllipse.get(1)).intValue();
						bUpdatePeriod = false;
						//System.out.println("\t\t\t\t\t2abbrev ellipse: "+strbWord.toString());
					}
					//check if the char after the abbrev is a period
					else if(strText.charAt(nIndex + strbWord.length()) == '.') {
						strbWord.append(".");
						nPeriodIndex = nIndex + strbWord.length()-1;
						//System.out.println("\t\t\t\t\t2abbrev period: "+strbWord.toString());
					}
					//if the char which follows the abbrev is not a period then update the nPeriodIndex only
					else {
						nPeriodIndex = nIndex + strbWord.length()-1;
						//System.out.println("\t\t\t\t\t2abbrev others: "+strbWord.toString());
					}
					//System.out.println("\t\t\t\tnew nPeriod: "+nPeriodIndex);
				}
			}
			
			if(!bFound || strbWord.length() <= 0) {
				strbWord = null;
			}
		}
		
		if(strbWord != null){
			vWordAndPeriodIndex = new Vector();
			vWordAndPeriodIndex.add(strbWord);
			vWordAndPeriodIndex.add(new Integer(nPeriodIndex));
			vWordAndPeriodIndex.add(new Boolean(bUpdatePeriod));
			vWordAndPeriodIndex.trimToSize();
		}
		
		return vWordAndPeriodIndex;
	}

/**
 *	input: There she goes...again. Hi ... there. I came. . .from above. You did . . . .. 12... Mr.... haha. Mr... Ho. 12.. larry yeah.Mr...Lo.Mr...La.
 *	output: 
 *		There she goes...again.
 *		Hi ... there.
 *		I came. . .from above.
 *		You did . . . .. 12... Mr.... haha. 
 *		Mr..
 *		.
 *		Ho.
 *		12.
 *		.
 *		larry yeah.
 *		Mr..
 *		.
 *		Lo.
 *		Mr..
 *		.
 *		La.
 *	input: ...23.87.454...676767.78.998.9009...
 *	output:
 *		...23.87.
 *		454...676767.78.
 *		998.9009...
 *	input: ...Mr. AB....Mr. at.wt.Ms....Mr....Mrs...
 *	output:
 *		...Mr. AB....Mr. at.wt.
 *		Ms....Mr....Mrs..
 *		.
 *	input: The molecule has  at....wt. which is 9.
 *	output: 
 *		The molecule has  at....wt. which is 9.
 */

	protected Vector checkIfPartOfEllipse(int nIndex, int nPeriodIndex) {
		//System.out.println("checkIfPartOfEllipse");
		//includes white spaces
		Vector vEllipseAndPeriodIndex = null;
		StringBuffer strbWord = new StringBuffer();
		int nPeriods = 0;

		for(int i = nIndex; i < strText.length() && (Character.isWhitespace(strText.charAt(i)) || strText.charAt(i) == '.'); i++) {
			if(strText.charAt(i) == '.') {
				nPeriods++;
			}
			strbWord.append(strText.charAt(i));
		}
		
		if (nPeriods >= 3) {
			//System.out.println("nPeriods: "+nPeriods);
			//System.out.println("ellipse: "+strbWord);
			nPeriodIndex = nIndex + strbWord.length()-1;
		}else{
			strbWord = null;
		}
		
		if(strbWord != null){
			vEllipseAndPeriodIndex = new Vector();
			vEllipseAndPeriodIndex.add(strbWord);
			vEllipseAndPeriodIndex.add(new Integer(nPeriodIndex));
			vEllipseAndPeriodIndex.trimToSize();
		}
		
		return vEllipseAndPeriodIndex;
	}
	
/**
 *	input: Where are you heading???    !!! ! # In the great lake ! What? ! You are going there? But why?
 *	output:
 *		Where are you heading??? !!! !
 *		# In the great lake !
 *		What? !
 *		You are going there?
 *		But why?
 */

	protected StringBuffer checkForMoreQuestionMarkOrExclamationPt(int nIndex) {
		//System.out.println("checkForMoreQuestionMarkOrExclamationPt");
		//includes white spaces
		StringBuffer strbWord = new StringBuffer();
		int nMarks = 0;

		for(int i = nIndex; i < strText.length() && (Character.isWhitespace(strText.charAt(i)) || strText.charAt(i) == '?' || strText.charAt(i) == '!'); i++) {
			if(strText.charAt(i) == '?' || strText.charAt(i) == '!') {
				nMarks++;
			}
			strbWord.append(strText.charAt(i));
		}
		
		if (nMarks > 1) {
			//System.out.println("multiple ? | ! : "+strbWord);
			//System.out.println("nMarks: "+nMarks);
		}else{
			strbWord = null;
		}
		
		return strbWord;
	}

	/**
	 * Find the full stop and verify what comes before
	 * the dot and if its something that exists in the
	 * regular expressions database verifies what comes
	 * after it as well.
	 */
	public void identifySentencesUsingRegularExpressions() {
		int nCurrentIndex = 0, nPeriodIndex = -1, nNewPeriodIndex = 0;
		StringBuffer strbSentence = new StringBuffer();
		StringBuffer strbWord;
		Vector vAbbrevOrEllipse;
		vSegmentedText = new Vector();

		for (nCurrentIndex = 0; nCurrentIndex < strText.length(); nCurrentIndex++) {
			//if a period is encountered
			if (strText.charAt(nCurrentIndex) == '.') {
				//System.out.println("period encountered");
		
				//check if the period is a decimal point (attached to a number)
				if ((strbWord = checkIfDecimalPt(nCurrentIndex, nPeriodIndex)) != null) {
					//if it is a decimal pt, append the number to the sentence
					//and to ensure that the number will not be appended again, 
					//save the last index
					strbSentence.append(strbWord);
					nCurrentIndex = nCurrentIndex + strbWord.length() - 1;
				}
				//check if the period is part of an abbrev
				else if ((vAbbrevOrEllipse = checkIfPartOfAbbrev(nCurrentIndex, nPeriodIndex)) != null) {
					//if it is part of an abbrev then append the abbrev to the 
					//sentence and to ensure that the abbrev will not be appended
					//again, save the last index
					strbWord = (StringBuffer)vAbbrevOrEllipse.get(0);
					strbSentence.append(strbWord);
					nCurrentIndex = nCurrentIndex + strbWord.length() - 1;
					nNewPeriodIndex = ((Integer)vAbbrevOrEllipse.get(1)).intValue();
					if(nPeriodIndex != nNewPeriodIndex) {
						if(((Boolean)vAbbrevOrEllipse.get(2)).booleanValue()) {
							strbSentence = addNewSegmentedSentence(strbSentence);
						}
						nPeriodIndex = nNewPeriodIndex;
					}
				}
				//check if the period is a part of ellipse
				else if ((vAbbrevOrEllipse = checkIfPartOfEllipse(nCurrentIndex, nPeriodIndex)) != null) {
					//if it is part of an ellipse then append the ellipse to the 
					//sentence and to ensure that the ellipse will not be appended
					//again, save the last index
					strbWord = (StringBuffer)vAbbrevOrEllipse.get(0);
					strbSentence.append(strbWord);
					nCurrentIndex = nCurrentIndex + strbWord.length() - 1;
					nPeriodIndex = ((Integer)vAbbrevOrEllipse.get(1)).intValue();
				}
				//it is simply just the end of a sentence
				else {
					//System.out.println("\tordinary period");
					strbSentence.append(strText.charAt(nCurrentIndex));
					//System.out.println("\tsentence: "+strbSentence);
					strbSentence = addNewSegmentedSentence(strbSentence);
					nPeriodIndex = nCurrentIndex;
				}
			}else if ((strText.charAt(nCurrentIndex) == '?') || (strText.charAt(nCurrentIndex) == '!')) {
				if((strbWord = checkForMoreQuestionMarkOrExclamationPt(nCurrentIndex)) != null) {
					strbSentence.append(strbWord);
					nCurrentIndex = nCurrentIndex + strbWord.length() - 1;
				}else {
					//System.out.println("single ? | ! mark");
					strbSentence.append(strText.charAt(nCurrentIndex));
				}
				strbSentence = addNewSegmentedSentence(strbSentence);
/*			}
			//check if the current char is a newline character, 
			//if it is, then consider the chars before it as a sentence
			//ignore the newline character
			else if(strText.charAt(nCurrentIndex) == '\n') {
				//System.out.println("new line: "+strbSentence);
				strbSentence = addNewSegmentedSentence(strbSentence);
*/			}else {
				//System.out.println("ordinary char: " + strText.charAt(nCurrentIndex));
				strbSentence.append(strText.charAt(nCurrentIndex));
			}
		}
		
		//for the remaining text not yet added as a sentence
		addNewSegmentedSentence(strbSentence);
		
		vSegmentedText.trimToSize();
	}

	/**
	 * Gets the strText with its sentence boundaries identified. 
	 * 
	 * @return the strText  with its sentence boundaries identified.
	 */
	public Vector getSentences() {
		return vSegmentedText;
	}
}